#ifndef COMMON_BASE_H
#define COMMON_BASE_H

#include <vector>
#include <exception>
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <spdlog/spdlog.h>

#include "Config.h"
#include "Reference.h"

constexpr scalar fOne      = glm::one<scalar>();
constexpr scalar fZero     = glm::zero<scalar>();
constexpr scalar fPi       = glm::pi<scalar>();
constexpr scalar fHalfPi   = glm::half_pi<scalar>();
constexpr scalar fEpsilon  = glm::epsilon<scalar>();
constexpr scalar fEpsilon2 = fEpsilon * fEpsilon;

using uint      = unsigned int;
using uvec2     = glm::uvec2;
using uvec3     = glm::uvec3;
using TriArray  = std::vector<uvec3>;
using EdgeArray = std::vector<uvec2>;

using vec2 = glm::tvec2<scalar>;
using vec3 = glm::tvec3<scalar>;
using vec4 = glm::tvec4<scalar>;
using mat2 = glm::tmat2x2<scalar>;
using mat3 = glm::tmat3x3<scalar>;
using mat4 = glm::tmat4x4<scalar>;

using ScalarArray = std::vector<scalar>;
using Vec2Array   = std::vector<vec2>;
using Vec3Array   = std::vector<vec3>;
using Vec4Array   = std::vector<vec4>;
using Mat2Array   = std::vector<mat2>;
using Mat3Array   = std::vector<mat3>;
using Mat4Array   = std::vector<mat4>;
using IntArray    = std::vector<int>;
using UIntArray   = std::vector<uint>;
using StringArray = std::vector<std::string>;

template <typename T>
using RefPtrArray = std::vector<RefPtr<T>>;


class AABB
{
public:
    explicit AABB(const vec3& minc, const vec3& maxc) noexcept : _min(minc), _max(maxc) {}

    AABB() noexcept = default;

    ~AABB() noexcept = default;

    AABB(const AABB&) noexcept = default;

    AABB(AABB&&) noexcept = default;

    AABB& operator=(const AABB&) noexcept = default;

    AABB& operator=(AABB&&) noexcept = default;

    [[nodiscard]] bool valid() const { return glm::all(glm::lessThanEqual(_min, _max)); }

    [[nodiscard]] const vec3& min() const { return _min; }

    [[nodiscard]] const vec3& max() const { return _max; }

    [[nodiscard]] vec3 center() const { return (_min + _max) * 0.5; }

    void expand(const vec3& v)
    {
        _min = glm::min(_min, v);
        _max = glm::max(_max, v);
    }

    void expand(const AABB& v)
    {
        expand(v._min);
        expand(v._max);
    }

    [[nodiscard]] bool contain(const vec3& v) const
    {
        return glm::all(glm::lessThanEqual(v, _max)) && glm::all(glm::greaterThanEqual(v, _min));
    }

    [[nodiscard]] bool overlap(const AABB& o) const
    {
        return ((_min.x >= o._min.x && _min.x <= o._max.x) || (o._min.x >= _min.x && o._min.x <= _max.x)) &&
               ((_min.y >= o._min.y && _min.y <= o._max.y) || (o._min.y >= _min.y && o._min.y <= _max.y)) &&
               ((_min.z >= o._min.z && _min.z <= o._max.z) || (o._min.z >= _min.z && o._min.z <= _max.z));
    }

private:
    vec3 _min = vec3(FLT_MAX, FLT_MAX, FLT_MAX);
    vec3 _max = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
};


inline std::string to_lower(std::string s)
{
    std::transform(s.begin(), s.end(), s.begin(), tolower);
    return s;
}


class Exception : public std::exception
{
    std::string _msg;

public:
    explicit Exception(std::string msg) : _msg(std::move(msg)) {}

#ifdef _MSC_VER
    _NODISCARD char const* what() const override {return _msg.c_str();}
#else
    const char* what() const _NOEXCEPT override { return _msg.c_str(); }
#endif
};

#endif //COMMON_BASE_H
